/*
 * Copyright 2023 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "src/gpu/AtlasTypes.h"
#include "src/text/gpu/VertexFiller.h"
#include "src/gpu/graphite/Device.h"
#include "src/base/SkZip.h"
#include "src/gpu/graphite/DrawWriter.h"
#include "src/gpu/graphite/Renderer.h"
#include "src/gpu/graphite/RendererProvider.h"
#include "src/text/gpu/Glyph.h"
#include "src/text/gpu/SubRunAllocator.h"
#include "src/text/gpu/SubRunContainer.h"

namespace sktext::gpu {
struct AtlasPt {
    uint16_t u;
    uint16_t v;
};

void VertexFiller::fillInstanceData(skgpu::graphite::DrawWriter *dw, int offset, int count, unsigned short flags,
    skvx::ushort2 ssboIndex, SkSpan<const Glyph *> glyphs, SkScalar depth) const
{
    auto quadData = [&]() { return SkMakeZip(glyphs.subspan(offset, count), fLeftTop.subspan(offset, count)); };

    skgpu::graphite::DrawWriter::Instances instances{ *dw, {}, {}, 4 };
    instances.reserve(count);
    // Need to send width, height, uvPos, xyPos, and strikeToSourceScale
    // pre-transform coords = (s*w*b_x + t_x, s*h*b_y + t_y)
    // where (b_x, b_y) are the vertexID coords
    for (auto [glyph, leftTop] : quadData()) {
        auto [al, at, ar, ab] = glyph->fAtlasLocator.getUVs();
        instances.append(1) << AtlasPt{ uint16_t(ar - al), uint16_t(ab - at) } <<
            AtlasPt{ uint16_t(al & 0x1fff), at } << leftTop << /* index= */ uint16_t(al >> 13) << flags << 1.0f <<
            depth << ssboIndex;
    }
}

using Rect = skgpu::graphite::Rect;
using Transform = skgpu::graphite::Transform;

std::tuple<Rect, Transform> VertexFiller::boundsAndDeviceMatrix(const Transform &localToDevice,
    SkPoint drawOrigin) const
{
    // The baked-in matrix differs from the current localToDevice by a translation if the
    // upper 2x2 remains the same, and there's no perspective. Since there's no projection,
    // Z is irrelevant, so it's okay that fCreationMatrix is an SkMatrix and has
    // discarded the 3rd row/col, and can ignore those values in localToDevice.
    const SkM44 &positionMatrix = localToDevice.matrix();
    const bool compatibleMatrix = positionMatrix.rc(0, 0) == fCreationMatrix.rc(0, 0) &&
        positionMatrix.rc(0, 1) == fCreationMatrix.rc(0, 1) && positionMatrix.rc(1, 0) == fCreationMatrix.rc(1, 0) &&
        positionMatrix.rc(1, 1) == fCreationMatrix.rc(1, 1) && localToDevice.type() != Transform::Type::kProjection &&
        !fCreationMatrix.hasPerspective();

    if (compatibleMatrix) {
        const SkV4 mappedOrigin = positionMatrix.map(drawOrigin.x(), drawOrigin.y(), 0.f, 1.f);
        const SkV2 offset = { mappedOrigin.x - fCreationMatrix.getTranslateX(),
            mappedOrigin.y - fCreationMatrix.getTranslateY() };
        if (SkScalarIsInt(offset.x) && SkScalarIsInt(offset.y)) {
            // The offset is an integer (but make sure), which means the generated mask can be
            // accessed without changing how texels would be sampled.
            return { Rect(fCreationBounds),
                Transform(SkM44::Translate(SkScalarRoundToInt(offset.x), SkScalarRoundToInt(offset.y))) };
        }
    }

    // Otherwise compute the relative transformation from fCreationMatrix to
    // localToDevice, with the drawOrigin applied. If fCreationMatrix or the
    // concatenation is not invertible the returned Transform is marked invalid and the draw
    // will be automatically dropped.
    const SkMatrix viewDifference = this->viewDifference(localToDevice.preTranslate(drawOrigin.x(), drawOrigin.y()));
    return { Rect(fCreationBounds), Transform(SkM44(viewDifference)) };
}
} // namespace sktext::gpu
